#!/usr/bin/python
# -*- coding:utf-8 -*-
u"""
http://msdn.microsoft.com/en-us/library/ms724947(v=VS.85).aspx
BOOL WINAPI SystemParametersInfo(
  __in     UINT uiAction,
  __in     UINT uiParam,
  __inout  PVOID pvParam,
  __in     UINT fWinIni
);
uiAction=SPI_SETDESKWALLPAPER
0x0014
Sets the desktop wallpaper. The value of the pvParam parameter determines the new wallpaper.
To specify a wallpaper bitmap, set pvParam to point to a null-terminated string containing the full path to the bitmap file.
Setting pvParam to "" removes the wallpaper.
Setting pvParam to SETWALLPAPER_DEFAULT or NULL reverts to the default wallpaper.

The pvParam parameter can specify a .jpg file.

Windows Server 2003 and Windows XP/2000:  The pvParam parameter cannot specify a .jpg file.


typedef struct _RECT {
  LONG left;
  LONG top;
  LONG right;
  LONG bottom;
} RECT, *PRECT;


"""

import ctypes
from optparse import OptionParser
import sys
import Image
import _winreg

if sys.platform != "win32" :
    sys.stderr.write("This script run only win32.\n")
    sys.exit(1)

SPI_SETDESKWALLPAPER = 0x0014
SM_CMONITORS = 80

# def functions
def fitWidth(srcImage, dstWidth, dstHeight, borderColor='black') :
    u"""幅がdstの幅になるようにsrcを拡大縮小する

    幅がdstの幅になるようにsrcを拡大縮小する。
    拡大縮小後、高さがdstのより大きい場合はトリミング、小さい場合は帯を入れる
    """
    scaleWidth = dstWidth
    scaleHeight = dstHeight
    (srcWidth, srcHeight) = srcImage.size
    scaleHeight = srcHeight * dstWidth / srcWidth

    tmpImage = srcImage.resize((scaleWidth, scaleHeight), Image.ANTIALIAS)

    if scaleHeight > dstHeight :        # triming
        dstImage = tmpImage.crop((0, (scaleHeight - dstHeight) / 2, scaleWidth,  ((scaleHeight - dstHeight) /2) + dstHeight))
    elif scaleHeight < dstHeight :      # add border
        dstImage = Image.new('RGB', (dstWidth, dstHeight), borderColor)
        dstImage.paste(tmpImage, (0, (dstHeight - scaleHeight) /2))

    return dstImage


def fitHeight(srcImage, dstWidth, dstHeight, borderColor='black') :
    u"""高さがdstの高さになるようにsrcを拡大縮小する

    高さがdstの高さになるようsrcを拡大縮小する。
    拡大縮小後、幅がdstより大きい場合はトリミング、小さい場合は帯を入れる
    """
    scaleWidth = dstWidth
    scaleHeight = dstHeight
    (srcWidth, srcHeight) = srcImage.size
    scaleWidth = srcWidth * dstHeight / srcHeight
    tmpImage = srcImage.resize((scaleWidth, scaleHeight), Image.ANTIALIAS)

    if scaleWidth > dstWidth :        # triming
        dstImage = tmpImage.crop(((scaleWidth - dstWidth)/2, 0, (scaleWidth - dstWidth)/2+dstWidth, dstHeight))
    elif scaleWidth < dstWidth :      # add border
        dstImage = Image.new('RGB', (dstWidth, dstHeight), borderColor)
        dstImage.paste(tmpImage, ((dstWidth - scaleWidth) /2, 0))

    return dstImage


def centerCrip(srcImage, dstWidth, dstHeight, borderColor) :
    u"""拡大縮小は行わず画面の中心を画像の中心として配置(壁紙の「中央」と一緒)
    """
    scaleWidth = dstWidth
    scaleHeight = dstHeight
    (srcWidth, srcHeight) = srcImage.size

    cripRect = {'left'   : 0,
                'top'    : 0,
                'right'  : srcWidth,
                'bottom' : srcHeight, }
    pasteOffset = { 'width' : 0,
                     'height'  : 0, }

    if srcWidth > dstWidth :
        cripRect['left'] = (srcWidth - dstWidth) / 2
        cripRect['right'] = srcWidth - ((srcWidth - dstWidth)/2)
    else :
        pasteOffset['width'] = (dstWidth - srcWidth) /2

    if srcHeight > dstHeight :
        cripRect['top'] = (srcHeight - dstHeight) / 2
        cripRect['bottom'] = srcHeight - ((srcHeight - dstHeight)/2)
    else :
        pasteOffset['height'] = (dstHeight - srcHeight) /2
    cripBox = (cripRect['left'],
               cripRect['top'],
               cripRect['right'],
               cripRect['bottom'],)
    tmpImage = srcImage.crop(cripBox)
    dstImage = Image.new('RGB', (dstWidth, dstHeight), borderColor)
    dstImage.paste(tmpImage, (pasteOffset['width'], pasteOffset['height']))
    return dstImage


def enum_values(key):
    u"""_winreg.EnumValue(key, index) を for 文で使える形に

    参考:
    http://lightson.dip.jp/zope/ZWiki/214Windows_e3_83_ac_e3_82_b8_e3_82_b9_e3_83_88_e3_83_aa_e3_81_ab_e3_82_a2_e3_82_af_e3_82_bb_e3_82_b9_e3_81_99_e3_82_8b
    """
    try:
        i = 0
        while True:
            yield _winreg.EnumValue(key, i)
            i += 1
    except EnvironmentError:
        pass


def read_values(key):
    u"""開かれているレジストリの値を辞書で返す

    参考:
    http://lightson.dip.jp/zope/ZWiki/214Windows_e3_83_ac_e3_82_b8_e3_82_b9_e3_83_88_e3_83_aa_e3_81_ab_e3_82_a2_e3_82_af_e3_82_bb_e3_82_b9_e3_81_99_e3_82_8    """
    values = {}
    for name, value, index in enum_values(key):
        values[name] = value
    return values


def upsertRegSz(path, key, value, regList=None) :
    if regList == None :
        reg = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, path)
        regList = read_values(reg)
        _winreg.closeKey(reg)
    if ((key in regList) and (regList[key] != value)) or (key not in regList) :
        regWrite = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, path, 0, _winreg.KEY_WRITE)
        try :
            _winreg.SetValueEx(regWrite,
                               key,
                               0,
                               _winreg.REG_SZ,
                               value)
        finally :
            _winreg.closeKey(regWrite)


# main

# get command line

parser = OptionParser(usage="""usage: %prog [options] image_filename(for 1st sceeen) image_filename(for 2nd sceeen) ...
example 1:%prog -f w -f w foo.jpg bar.jpg -o C:\\walpaper.bmp
\tset foo.jpg to screen 1 and fit width(scaling image width to equal screen 1 width), and
\tset bar.jpg to screen 2 and fit width.
\tthen combined image set to wallpaper and save C:\\walpaper.bmp.
\tsaved C:\\wallpaper.bmp is used at reset or off/on this machine.

example 2:%prog -b black -f w -f h none bar.jpg
\tnot set screen 1, background color is 'black'.
\tset bar.jpg to screen 2 and fit width.
\tthen combined image set to wallpaper and save default C:\\walpaper.bmp. 
\tsaved C:\\wallpaper.bmp is used at reset or off/on this machine.
""")
parser.add_option("-f", "--fit", action="append", dest="fit", default=[],
                  help="fit pattern CODE h:fitHeight, w:fitWidth, c:centering", metavar="CODE")
parser.add_option("-b", "--background-color", action="append", dest="backgroundColor", default=[],
                  help="specify background COLOR code. default is black", metavar="COLOR")
parser.add_option("-o", "--output-path", dest="outPath", default="C:\\wallpaper.bmp",
                  help="output wallpaper bmp FULL-PATH-FILENAME. set to registory and use reset or off/on."
                  , metavar="FULL-PATH-FILENAME")
(options, args) = parser.parse_args()

if len(args) < 1 :
    sys.stderr.write("specify image_filename\n")
    sys.exit(1)
if len(options.fit) < len(args) :
    for i in range(0, len(args) - len(options.fit)) :
        options.fit.append('c')
if len(options.backgroundColor) < len(args) :
    for i in range(0, len(args) - len(options.backgroundColor)) :
        options.backgroundColor.append('black')


user32 = ctypes.windll.LoadLibrary('user32.dll')

MONITOR_ENUM_PROC = ctypes.CFUNCTYPE(ctypes.c_bool,
                                     ctypes.c_int,
                                     ctypes.c_int,
                                     ctypes.POINTER(ctypes.c_int),      # RECT
                                     ctypes.c_int)

monitorRectList = []
def pyMonitorEnumProc(hMonitor,
                       hdcMonitor,
                       lprcMonitor,
                       dwData) :
    monitorRect = { 'left'   : lprcMonitor[0],
                    'top'    : lprcMonitor[1],
                    'right'  : lprcMonitor[2],
                    'bottom' : lprcMonitor[3], }
    monitorRectList.append(monitorRect)
    return True

enum_proc = MONITOR_ENUM_PROC(pyMonitorEnumProc)
result = user32.EnumDisplayMonitors(0, 0, enum_proc, 0)

# get bound rect
boundRect = { 'left'   : 0,
              'top'    : 0,
              'right'  : 0,
              'bottom' : 0, }
for rect in monitorRectList :
    if rect['left'] < boundRect['left'] :
        boundRect['left'] = rect['left']
    if rect['top'] < boundRect['top'] :
        boundRect['top'] = rect['top']
    if rect['right'] > boundRect['right'] :
        boundRect['right'] = rect['right']
    if rect['bottom'] > boundRect['bottom'] :
        boundRect['bottom'] = rect['bottom']

boundRectSize = { 'width'  : boundRect['right'] - boundRect['left'],
                  'height' : boundRect['bottom'] - boundRect['top'],}

boundImage = Image.new('RGB', (boundRectSize['width'], boundRectSize['height']), options.backgroundColor[0])

wallImageList = []
for arg in args :
    if arg == "none" :
        wallImageList.append(None)
    else :
        wallImageList.append(Image.open(arg))

for i, monitorRect in enumerate(monitorRectList) :
    # fit to monitor size
    if i < len(wallImageList) :
        monitorSize = { 'width'  : monitorRect['right'] - monitorRect['left'],
                        'height' : monitorRect['bottom'] - monitorRect['top'], }

        baseImage = Image.new('RGB', (monitorSize['width'], monitorSize['height']), options.backgroundColor[i])
        boundImage.paste(baseImage, (monitorRect['left'] - boundRect['left'],
                                     monitorRect['top'] - boundRect['top']))

        if wallImageList[i] == None :
            continue

        if options.fit[i] == 'w' :
            dstImage = fitWidth(wallImageList[i], monitorSize['width'], monitorSize['height'], options.backgroundColor[i])
        elif options.fit[i] == 'h' :
            dstImage = fitHeight(wallImageList[i], monitorSize['width'], monitorSize['height'], options.backgroundColor[i])
        elif options.fit[i] == 'c' :
            dstImage = centerCrip(wallImageList[i], monitorSize['width'], monitorSize['height'], options.backgroundColor[i])
        boundImage.paste(dstImage, (monitorRect['left'] - boundRect['left'],
                                    monitorRect['top'] - boundRect['top']))

boundImage.save(options.outPath)

if boundRect['left'] != 0 or boundRect['top'] != 0 :
    reg = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Control Panel\\Desktop")
    try:
        regList = read_values(reg)
    finally:
        _winreg.CloseKey(reg)
    upsertRegSz("Control Panel\\Desktop", 'TileWallpaper', u'1', regList)
    upsertRegSz("Control Panel\\Desktop", 'Wallpaper', options.outPath, regList)
    upsertRegSz("Control Panel\\Desktop", 'WallpaperStyle', u'0', regList)
    upsertRegSz("Control Panel\\Desktop", 'Wallpaperoriginx', str(boundRect['left']), regList)
    upsertRegSz("Control Panel\\Desktop", 'Wallpaperoriginy', str(boundRect['top']), regList)

# set wall papaer
user32.SystemParametersInfoA(SPI_SETDESKWALLPAPER, 0, options.outPath, 2 | 1)
